<?php
/**
 ***********************************************************************************************************************
 * 异常错误处理
 ***********************************************************************************************************************
 */

namespace artisan\exception;

use artisan\logger\logger;

/**
 * Class handler
 * @package artisan\exception
 */
class handler
{
    /**
     * @var array 异常&日志映射
     */
    private $errorLevelMap = array(
        E_ERROR             => 'CRITICAL',
        E_WARNING           => 'WARNING',
        E_PARSE             => 'ALERT',
        E_NOTICE            => 'NOTICE',
        E_CORE_ERROR        => 'CRITICAL',
        E_CORE_WARNING      => 'WARNING',
        E_COMPILE_ERROR     => 'ALERT',
        E_COMPILE_WARNING   => 'WARNING',
        E_USER_ERROR        => 'ERROR',
        E_USER_WARNING      => 'WARNING',
        E_USER_NOTICE       => 'NOTICE',
        E_STRICT            => 'NOTICE',
        E_RECOVERABLE_ERROR => 'ERROR',
        E_DEPRECATED        => 'NOTICE',
        E_USER_DEPRECATED   => 'NOTICE',
    );

    /**
     * @var int 默认错误
     */
    private $errorReporting = E_ALL;

    /**
     * 初始化异常处理
     *
     * @param int $errorReporting
     */
    public function __construct($errorReporting = E_ALL)
    {
        // 生产环境忽视用户设定E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR
        $errorReporting = empty($errorReporting) ? E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR  : $errorReporting;
        $this->errorReporting  = (env() == 'product') ? E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR : $errorReporting;
        //myprint($errorReporting);
        // 关闭系统错误输出
        ini_set('display_errors', 0);
        // 注册错误处理方法
        set_error_handler(array($this, 'handleError'), $this->errorReporting);
        // 注册异常处理方法
        set_exception_handler(array($this, 'handleException'));
        // 注册致命错误处理方法
        register_shutdown_function(array($this, 'handleFatalError'));
    }

    /**
     * 捕获错误
     *
     * @param $code
     * @param $message
     * @param string $file
     * @param int $line
     * @param array $context
     */
    public function handleError($code, $message, $file = '', $line = 0, $context = array())
    {
        // 记录日志
        logger::add($this->errorLevelMap[$code], $this->errorLevelMap[$code] . ': '.$message . ' at ' . $file . ' ' . $line, $context);
        // 展示错误
        $this->displayError($code, $message, $file, $line);
    }

    /**
     * 捕获致命错误
     */
    public function handleFatalError()
    {
        $this->reservedMemory = null;
        $lastError = error_get_last();
        if($lastError && ($this->errorReporting & $lastError['type'])) {
            // 记录日志
            logger::add($this->errorLevelMap[$lastError['type']], $this->errorLevelMap[$lastError['type']] . ': '.$lastError['message'] . ' at ' . $lastError['file'] . ' ' . $lastError['line']);
            // 展示错误
            $this->displayError($lastError['type'], $lastError['message'], $lastError['file'], $lastError['line']);
        }
    }

    /**
     * 捕获异常
     *
     * @param \Exception|\Error $e
     */
    public function handleException($e)
    {
        restore_error_handler();
        restore_exception_handler();
        // 记录日志
        logger::add(logger::ERROR, sprintf('%s: "%s" at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()));
        // 抛出状态码为1001表示影响到系统服务
        $this->displayError($e->getCode() ?: 1001, get_class($e) . ': ' . $e->getMessage(), $e->getFile(), $e->getLine());
    }

    /**
     * 展示错误
     *
     * @param $code
     * @param $message
     * @param $file
     * @param $line
     */
    public function displayError($code, $message, $file, $line)
    {
        //记录错误详情
        //http::post('http://api.kuaidihelp.com/v1/MyTest/remoteLogFile',['message'=>func_get_args()],['TIMEOUT'=>1]);
        $code = $this->codeToString($code);
        switch(env()) {
            case 'develop':
                $msg = '';
                $msg .= "<h1>PHP [$code]</h1>\n";
                $msg .= "<p>$message ($file:$line)</p>\n";
                $msg .= '<pre>';
                $trace = debug_backtrace();
                if (count($trace) > 3)
                    $trace = array_slice($trace, 3);
                foreach ($trace as $i => $t) {
                    if (!isset($t['file']))
                        $t['file'] = 'unknown';
                    if (!isset($t['line']))
                        $t['line'] = 0;
                    if (!isset($t['function']))
                        $t['function'] = 'unknown';
                    $msg .= "#$i {$t['file']}({$t['line']}): ";
                    if (isset($t['object']) && is_object($t['object']))
                        $msg .= get_class($t['object']) . '->';
                    $msg .= "{$t['function']}()\n";
                }
                $msg .= '</pre>';
                // 开发环境展示错误信息
                raise(1001, $msg);
                break;
            case 'product':
                /*$msg = '';
                $msg .= "<h1>PHP [$code]</h1>\n";
                $msg .= "<p>$message ($file:$line)</p>\n";
                $msg .= '<pre>';
                $trace = debug_backtrace();
                if (count($trace) > 3)
                    $trace = array_slice($trace, 3);
                foreach ($trace as $i => $t) {
                    if (!isset($t['file']))
                        $t['file'] = 'unknown';
                    if (!isset($t['line']))
                        $t['line'] = 0;
                    if (!isset($t['function']))
                        $t['function'] = 'unknown';
                    $msg .= "#$i {$t['file']}({$t['line']}): ";
                    if (isset($t['object']) && is_object($t['object']))
                        $msg .= get_class($t['object']) . '->';
                    $msg .= "{$t['function']}()\n";
                }
                $msg .= '</pre>';*/


                // 生产环境屏蔽错误信息展示
                raise(1001, '网络忙，请稍后重试!error:'/*.$msg*/);
                break;
        }
    }

    /**
     * PHP错误映射转换
     *
     * @param $code
     * @return string
     */
    private function codeToString($code)
    {
        switch ($code) {
            case E_ERROR:
                return 'E_ERROR';
            case E_WARNING:
                return 'E_WARNING';
            case E_PARSE:
                return 'E_PARSE';
            case E_NOTICE:
                return 'E_NOTICE';
            case E_CORE_ERROR:
                return 'E_CORE_ERROR';
            case E_CORE_WARNING:
                return 'E_CORE_WARNING';
            case E_COMPILE_ERROR:
                return 'E_COMPILE_ERROR';
            case E_COMPILE_WARNING:
                return 'E_COMPILE_WARNING';
            case E_USER_ERROR:
                return 'E_USER_ERROR';
            case E_USER_WARNING:
                return 'E_USER_WARNING';
            case E_USER_NOTICE:
                return 'E_USER_NOTICE';
            case E_STRICT:
                return 'E_STRICT';
            case E_RECOVERABLE_ERROR:
                return 'E_RECOVERABLE_ERROR';
            case E_DEPRECATED:
                return 'E_DEPRECATED';
            case E_USER_DEPRECATED:
                return 'E_USER_DEPRECATED';
            case 1001:
                return 'Exception';
        }

        return 'Unknow Error';
    }

}
